package com.kongjs.application.service.impl;

import com.kongjs.application.config.JwtProperties;
import com.kongjs.application.exception.TokenException;
import com.kongjs.application.model.dto.JwtTokenDTO;
import com.kongjs.application.model.dto.UserDetailsDTO;
import com.kongjs.application.service.JwtService;
import com.kongjs.common.mybatis.user.UserContextHolder;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Instant;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Service
public class JwtServiceImpl implements JwtService, InitializingBean {

    @Resource
    private JwtProperties jwtProperties;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public JwtTokenDTO generateToken(UserDetailsDTO dto, boolean isRefreshToken) {
        try {
            Instant now = Instant.now();
            Instant expiryTime;
            if (!isRefreshToken) {
                expiryTime = now.plus(jwtProperties.getAccessTokenExpiry());
            } else {
                expiryTime = now.plus(jwtProperties.getRefreshTokenExpiry());
            }
            Date nowTime = new Date(now.toEpochMilli());
            Date expTime = new Date(expiryTime.toEpochMilli());
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            // jwt 权限字段 空格隔开 scope scp
            // SCOPE_ 存储权限前缀
            JWTClaimsSet claims = new JWTClaimsSet.Builder()
                    .issuer(jwtProperties.getIssuer())
                    .audience(jwtProperties.getAudience())
                    .subject(dto.getUsername())
                    .expirationTime(expTime)
                    .notBeforeTime(nowTime)
                    .issueTime(nowTime)
                    .jwtID(uuid)
                    .claim("userId", dto.getUserId())
                    .claim("tenantId", dto.getTenantId())
                    .build();

        /*if (!isRefreshToken) {
            RMap<Object, Object> accessTokenMap = redissonClient.getMap("token:access:" + uuid);
            accessTokenMap.put("sub", dto.getUsername());
            accessTokenMap.expire(expiryTime);
        } else {
            RMap<Object, Object> refreshTokenMap = redissonClient.getMap("token:refresh:" + uuid);
            refreshTokenMap.put("sub", dto.getUsername());
            refreshTokenMap.expire(expiryTime);
        }*/

            JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES512).build();
            SignedJWT jwt = new SignedJWT(header, claims);
            try {
                if (!isRefreshToken) {
                    jwt.sign(accessTokenSigner);
                } else {
                    jwt.sign(refreshTokenSigner);
                }
            } catch (Exception ex) {
                throw new TokenException("token错误");
            }
            JwtTokenDTO tokenDTO = new JwtTokenDTO();
            tokenDTO.setToken(jwt.serialize());
            tokenDTO.setIss(jwtProperties.getIssuer());
            tokenDTO.setSub(dto.getUsername());
            tokenDTO.setAud(jwtProperties.getAudience());
            tokenDTO.setExp(expTime);
            tokenDTO.setNbf(nowTime);
            tokenDTO.setIat(nowTime);
            tokenDTO.setJti(uuid);
            tokenDTO.setUserId(dto.getUserId());
            tokenDTO.setTenantId(dto.getTenantId());
            return tokenDTO;
        } catch (Exception e) {
            throw new TokenException("token错误");
        }
    }

    @Override
    public JwtTokenDTO parseToken(String token, boolean isRefreshToken) {
        try {
            SignedJWT jwt = SignedJWT.parse(token);
            if (!isRefreshToken) {
                jwt.verify(accessTokenVerifier);
            } else {
                jwt.verify(refreshTokenVerifier);
            }
            JWTClaimsSet claims = jwt.getJWTClaimsSet();
            JwtTokenDTO dto = new JwtTokenDTO();
            dto.setToken(jwt.serialize());
            dto.setIss(jwtProperties.getIssuer());
            dto.setSub(claims.getSubject());
            dto.setAud(jwtProperties.getAudience());
            dto.setExp(claims.getExpirationTime());
            dto.setNbf(claims.getNotBeforeTime());
            dto.setIat(claims.getIssueTime());
            dto.setJti(claims.getJWTID());
            dto.setUserId(claims.getClaimAsString("userId"));
            dto.setTenantId(claims.getClaimAsString("tenantId"));
            return dto;
        } catch (JOSEException | ParseException e) {
            throw new TokenException("非法token");
        } catch (Exception e) {
            throw new TokenException("token错误");
        }
    }

    @Override
    public JwtTokenDTO refreshToken(String token) {
        try {
            JwtTokenDTO tokenDTO = parseToken(token, false);
            boolean isExp = tokenDTO.getExp().after(new Date());
            String subject = tokenDTO.getSub();
            String userId = tokenDTO.getUserId();
            String tenantId = tokenDTO.getTenantId();
            if (!isExp) {
                return tokenDTO;
            }
            if (!StringUtils.hasText(userId)) {
                throw new TokenException("token错误");
            }
            UserContextHolder.setUserId(userId);
            UserDetailsDTO dto = new UserDetailsDTO();
            dto.setTenantId(tenantId);
            dto.setUserId(userId);
            dto.setUsername(subject);
            return generateToken(dto, false);
        } catch (Exception e) {
            throw new TokenException("token错误");
        }
    }

    private JWSSigner accessTokenSigner;
    private JWSSigner refreshTokenSigner;
    private JWSVerifier accessTokenVerifier;
    private JWSVerifier refreshTokenVerifier;

    @Override
    public void afterPropertiesSet() throws Exception {
        String accessTokenPrivateKey = jwtProperties.getAccessTokenPrivateKey().getContentAsString(StandardCharsets.UTF_8);
        String accessTokenPublicKey = jwtProperties.getAccessTokenPublicKey().getContentAsString(StandardCharsets.UTF_8);

        String refreshTokenPrivateKey = jwtProperties.getRefreshTokenPrivateKey().getContentAsString(StandardCharsets.UTF_8);
        String refreshTokenPublicKey = jwtProperties.getRefreshTokenPublicKey().getContentAsString(StandardCharsets.UTF_8);

        accessTokenSigner = new ECDSASigner(ECKey.parse(accessTokenPrivateKey));
        refreshTokenSigner = new ECDSASigner(ECKey.parse(refreshTokenPrivateKey));

        accessTokenVerifier = new ECDSAVerifier(ECKey.parse(accessTokenPublicKey));
        refreshTokenVerifier = new ECDSAVerifier(ECKey.parse(refreshTokenPublicKey));
    }
}
